/******************************************************************************
 * Copyright 2022 The AIR Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#pragma once
#include <algorithm>
#include <cassert>
#include <memory>
#include <string>
#include <utility>

#include "base-headers/base-headers.hpp"

namespace base {
namespace memory {

template <typename T>
class RingArray final {
 public:
  using ptr_t = std::shared_ptr<RingArray>;
  using value_t =
      typename std::remove_cv<typename std::remove_reference<T>::type>::type;
  using func_free_cb_t = std::function<void(value_t *)>;

  static constexpr size_t kArrayMinSize = 256;
  // ===========================================================================
  static_assert(0 == (kArrayMinSize & (kArrayMinSize - 1)), "Illegal Number");
  static_assert(std::is_trivially_copyable<value_t>::value,
                "required trivially copyable type");
  static_assert(sizeof(value_t), "sizeof must > 0");
  // ===========================================================================
 private:
  const size_t array_size_;
  const size_t buffer_size_;
  value_t *const buffer_;
  const func_free_cb_t free_cb_;
  size_t loc_head_ = 0;
  size_t loc_tail_ = 0;
  // ===========================================================================
  inline size_t ArrayHead() const { return loc_head_ & (array_size_ - 1); }
  inline size_t ArrayTail() const { return loc_tail_ & (array_size_ - 1); }

  explicit RingArray(size_t array_size, func_free_cb_t cb = {})
      : array_size_(basic::AlignNumber(
            true, array_size < kArrayMinSize ? kArrayMinSize : array_size)),
        buffer_size_(array_size_ * sizeof(value_t)),
        buffer_(static_cast<value_t *>(
            aligned_alloc(sysconf(_SC_PAGESIZE), buffer_size_))),
        free_cb_(std::move(cb)) {
    assert(256 <= array_size_);
    assert(0 == (array_size_ & (array_size_ - 1)));
  }

  ~RingArray() {
    Clear();
    free(buffer_);
  }

 public:
  static ptr_t NewInstance(size_t array_size, func_free_cb_t cb = {}) noexcept {
    auto res =
        std::shared_ptr<RingArray>(new RingArray(array_size, std::move(cb)),
                                   [](RingArray *p) { delete p; });
    if (!res || !res->buffer_) {
      return nullptr;
    }
    return res;
  }
  /**
   * 清除 Buffer 中所有的数据
   */
  void Clear() {
    if (free_cb_) {
      value_t val;
      while (GetData(&val, 1)) {
        free_cb_(&val);
      }
    }
    loc_tail_ = 0;
    loc_head_ = 0;
  }

  /**
   * 获取缓冲区已被使用的长度
   * @return 已被使用的长度
   */
  size_t ArrayUsed() const { return loc_tail_ - loc_head_; }

  /**
   * 获取缓冲区可被使用的长度
   * @return 可被使用的长度
   */
  size_t ArrayAvailable() const { return array_size_ - ArrayUsed(); }

  /**
   * 将数据写入缓冲区结尾。如果缓冲区剩余长度不足，则写满为止，数据被截断，不会溢出。
   * @param buf  需要写入的数据
   * @param len  数据长度
   * @return     实际写入数据的长度
   */
  size_t PutData(const value_t *buf, size_t len) {
    if (!buf) {
      return 0;
    }
    len = std::min(len, ArrayAvailable());
    size_t tmp = std::min(len, array_size_ - ArrayTail());
    memcpy(buffer_ + ArrayTail(), buf, tmp * sizeof(value_t));
    memcpy(buffer_, buf + tmp, (len - tmp) * sizeof(value_t));
    loc_tail_ += len;
    return len;
  }

  /**
   * 从缓冲区提取数据。
   * @param buf  [OUT] 提取出的数据, 如果为空，则直接擦除数据
   * @param len  允许提取的最大长度
   * @return     实际提取的数据长度
   */
  size_t GetData(value_t *buf, size_t len) {
    if (!buf) {
      return 0;
    }
    len = std::min(len, ArrayUsed());
    size_t tmp = std::min(len, array_size_ - ArrayHead());
    memcpy(buf, buffer_ + ArrayHead(), tmp * sizeof(value_t));
    memcpy(buf + tmp, buffer_, (len - tmp) * sizeof(value_t));
    loc_head_ += len;
    return len;
  }
  std::string DebugInfo() const {
    std::stringstream res;
    res << "[ALL/AVAIL/IDX]: " << array_size_ << "/" << ArrayAvailable() << "/"
        << ArrayHead();
    return res.str();
  }
  // ===========================================================================
  RingArray(const RingArray &) = delete;
  RingArray &operator=(const RingArray &) = delete;
  RingArray(RingArray &&) = delete;
  RingArray &operator=(RingArray &&) = delete;
};

using RingBuffer = RingArray<char>;

}  // namespace memory
}  // namespace base
